#include "sc/context.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include "boost/algorithm/string/erase.hpp"

#include "boost/filesystem/operations.hpp"
#include "boost/filesystem/path.hpp"
#include "boost/regex.hpp"

#include "utils\traceur.h"

#include <sstream>

using namespace sc;


/**
* Context constructor
*/
Context::Context()
: m_sccDoc(NULL)
{
}

/**
* Context destructor
*/
Context::~Context()
{
    Clear();
}

/**
* Load a context
*
* @param sFilenameSCC SCC file name
*
* @return loading error
*/
int Context::Load(const std::string& sFilenameSCC)
{
    int error = SCE_SHEMA_INVALID;

    m_sFilename = sFilenameSCC;

    // Load the SCC document
    m_sccDoc = xmlReadFile(sFilenameSCC.c_str(), NULL, 0);
    xmlNodePtr root = xmlDocGetRootElement(m_sccDoc);

    if (root && !xmlStrcmp(root->name, xml::scc::_context))
    {
        error = SCE_NONE;

        xmlNodePtr child = root->children;
        while (child && !error)
        {
            if (child->type == XML_ELEMENT_NODE)
            {
                if (!xmlStrcmp(child->name, xml::scc::_rng))
                {
                    error = LoadConfig(child);
                }
                else if (!xmlStrcmp(child->name, xml::scc::_setup))
                {
                    error = LoadSetup(child);
                }
                else if (!xmlStrcmp(child->name, xml::scc::_mmi))
                {
                    error = LoadMMI(child);
                }
            }
            child = child->next;
        }
    }
    return error;
}

/**
* Load the configuration file (Relax NG)
*
* @param pNodeRNG
*
* @return loading error
*/
int Context::LoadConfig(const xmlNodePtr pNodeRNG)
{
    int error = SCE_SHEMA_INVALID;

    // Load the config
    const xmlChar* sRngFile = xmlNodeListGetString(m_sccDoc, pNodeRNG->children, 1);
    {
        boost::filesystem::path pathSCC(m_sFilename);
        boost::filesystem::path pathRNG(pathSCC.branch_path() / std::string((const char*) sRngFile));

        error = m_setup.LoadConfig(pathRNG.string());
    }
    return error;
}

/**
* Load the setup file
*
* @param pNodeSetup
*
* @return loading error
*/
int Context::LoadSetup(const xmlNodePtr pNodeSetup)
{
    int error = SCE_SHEMA_INVALID;

    // Load the config
    const xmlChar* sSetupFile = xmlNodeListGetString(m_sccDoc, pNodeSetup->children, 1);
    {
        boost::filesystem::path pathSCC(m_sFilename);
        boost::filesystem::path pathSetup(pathSCC.branch_path() / std::string((const char*) sSetupFile));

        error = m_setup.Load(pathSetup.string());
    }
    return error;
}

/**
* Load the MMI context
*  - the way to print label
*  - icon for the tree item
*
* @param pNodeMMI
*
* @return loading error
*/
int Context::LoadMMI(const xmlNodePtr pNodeMMI)
{
    int error = SCE_NONE;

    xmlNodePtr child = pNodeMMI->children;
    while (child && !error)
    {
        if (child->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(child->name, xml::scc::_label))
            {
                // LABEL
                const xmlChar* sFor = xmlGetProp(child, xml::scc::_for);
                if (sFor == NULL)
                {
                    error = SCE_SHEMA_INVALID;
                }
                else
                {
                    std::string sElement( (const char*) sFor );
                    const xmlChar* sValue = xmlGetProp(child, xml::scc::_value);
                    const xmlChar* sIcon  = xmlGetProp(child, xml::scc::_icon);

                    if (sValue)
                    {
                        // Load the label value
                        m_mapMMI[sElement].sValue = (const char*) sValue;
                    }
                    if (sIcon)
                    {
                        // Load the icon path
                        boost::filesystem::path pathSCC(m_sFilename);
                        boost::filesystem::path pathICO(pathSCC.branch_path() / (const char*) sIcon);
                        if (boost::filesystem::exists(pathICO))
                        {
                            m_mapMMI[sElement].sIcon = pathICO.native_file_string();
                        }
                    }
                }
            }
        }
        child = child->next;
    }
    return error;
}

/**
* Save the current context to the same file as before
*
* @return saving error
*/
int Context::Save(void) const
{
    int error = SCE_SCC_NOT_LOADED;

    if (!m_sFilename.empty())
    {
        error = SCE_CANT_SAVE_FILE;

        // Create the context doc writer
        xmlTextWriterPtr xmlWriter = xmlNewTextWriterFilename(m_sFilename.c_str(), 0);
        if (xmlWriter && !xmlTextWriterSetIndent(xmlWriter, 2))
        {
            // Add the xml root node
            if ( xmlTextWriterStartDocument(xmlWriter, NULL, NULL, NULL) >= 0
              && xmlTextWriterStartElement(xmlWriter, xml::scc::_context) >= 0
               )
            {
                error = SaveConfig(xmlWriter);
                if (!error)
                    error = SaveSetup(xmlWriter);
                if (!error)
                    error = SaveMMI(xmlWriter);

                // End document
                xmlTextWriterEndElement(xmlWriter);
                xmlTextWriterEndDocument(xmlWriter);
            }
            xmlFreeTextWriter(xmlWriter);
        }
        error = SCE_NONE;
    }
    return error;
}

/**
* Save the context to the specified file name
*
* @param sFilenameSCC
*
* @return saving error
*/
int Context::Save(const std::string& sFilenameSCC)
{
    m_sFilename = sFilenameSCC;
    return Save();
}

/**
* Save the configuration file in the context
*
* @param xmlWriter
*
* @return saving error
*/
int Context::SaveConfig(xmlTextWriterPtr xmlWriter) const
{
    int error = SCE_CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if (xmlTextWriterWriteElement(xmlWriter, xml::scc::_rng, (const xmlChar*) "setup.rng") >= 0)
	{
		error = SCE_NONE;
	}
    return error;
}

/**
* Save the setup file name in the context
*
* @param xmlWriter
*
* @return saving error
*/
int Context::SaveSetup(xmlTextWriterPtr xmlWriter) const
{
    int error = SCE_CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if (xmlTextWriterWriteElement(xmlWriter, xml::scc::_setup, (const xmlChar*) "setup.xml") >= 0)
	{
		error = SCE_NONE;
	}
    return error;
}

/**
* Save the MMI context, ie : label and icons
*
* @param xmlWriter
*
* @return saving error
*/
int Context::SaveMMI(xmlTextWriterPtr xmlWriter) const
{
	int error = SCE_CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if (xmlTextWriterStartElement(xmlWriter, xml::scc::_mmi) >= 0)
	{
		error = SCE_NONE;

        // Save all MMI information
        TMapMMI::const_iterator iter;
        for (iter = m_mapMMI.begin(); iter != m_mapMMI.end() && !error; iter++)
        {
            if (xmlTextWriterStartElement(xmlWriter, xml::scc::_label) < 0)
            {
                error = SCE_CANT_SAVE_FILE;
            }
            else
            {
                const TMMI& rcMMI = (*iter).second;

                // Attribute : for
                xmlTextWriterWriteAttribute( xmlWriter
                                           , xml::scc::_for
										   , (const xmlChar*) (*iter).first.c_str()
										   );

                if (!rcMMI.sValue.empty())
                {
                    // Attribute : value
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_value
                                               , (const xmlChar*) rcMMI.sValue.c_str()
                                               );
                }

                if (!rcMMI.sIcon.empty())
                {
                    // Attribute : icon
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_icon
                                               , (const xmlChar*) rcMMI.sIcon.c_str()
                                               );
                }

                xmlTextWriterEndElement(xmlWriter);
            }
        }
        xmlTextWriterEndElement(xmlWriter);
	}
	return error;
}

/**
* Get the label of the tree according to the context
*
* @param rcElement element of which we need the tree label
*
* @return label
*/
std::string Context::GetLabel(const Setup::Element& rcElement) const
{
    std::string sLabel = rcElement.GetName();
    TMapMMI::const_iterator iter = m_mapMMI.find(sLabel);
    if (iter != m_mapMMI.end())
    {
        sLabel = (*iter).second.sValue;

        // Label is dynamic ?
        std::string::const_iterator start = sLabel.begin();
        std::string::const_iterator end   = sLabel.end();
        boost::match_results<std::string::const_iterator> what;
        boost::regex re_find("%([a-z]*)%");
        boost::match_flag_type flags = boost::match_default;
        while(regex_search(start, end, what, re_find, flags))
        {
            std::string sMatch(what[1].first, what[1].second);

            // Does the attribute exist ?
            Setup::TMapAttribut::const_iterator iterAtt = rcElement.GetAttributes().find(sMatch);
            if (iterAtt != rcElement.GetAttributes().end())
            {
                if (!(*iterAtt).second.m_sValue.empty())
                {
                    // Create the regex to replace dynamic info
                    std::ostringstream oss;
                    oss << "(%" << sMatch << "%)";

                    boost::regex re_replace(oss.str());
                    sLabel = boost::regex_replace(sLabel, re_replace, (*iterAtt).second.m_sValue);
                }
                else
                {
                    // Attribute empty : We remove all to use the default value
                    sLabel.clear();
                    break;
                }
            }
            else
            {
                // Attribute doesn't exist : We remove all to use the default value
                sLabel.clear();
                break;
            }
            // update search position:
            start = what[0].second;
            // update flags:
            flags |= (boost::match_prev_avail | boost::match_not_bob);
        }

        // If label is empty, we fill it with the element name
        if (sLabel.empty())
            sLabel = rcElement.GetName();
    }
    return sLabel;
}

/**
* Set the config (Relax NG) file
*
* @param sFileRNG
*
* @return true if the RNG file exists and is valid
*/
bool Context::SetConfigFile(const std::string& sFileRNG)
{
    bool bExist = false;
    boost::filesystem::path pathSCC(m_sFilename);
    boost::filesystem::path pathRNG(pathSCC.branch_path() / sFileRNG);
    if (boost::filesystem::exists(pathRNG))
    {
        if (Config::IsRngValid(pathRNG.native_file_string()))
        {
            m_sFileRNG = sFileRNG;
            bExist = true;
        }
    }
    return bExist;
}

/**
* Clear the context.
*  Remove all information about it,
*  Remove setup.
*/
void Context::Clear(void)
{
    m_setup.Clear();
    m_mapMMI.clear();
    m_sFilename.clear();
    if (m_sccDoc)
    {
        xmlFreeDoc(m_sccDoc);
        m_sccDoc = NULL;
    }
}
